package com.linkoog.devtools.type.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linkoog.devtools.type.Type;
import com.linkoog.devtools.type.impl.LiteralTypes;
import com.linkoog.devtools.utils.ClassFieldsUtils;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;

public class TypeJsonDeserializer extends JsonDeserializer<Type> {


    @Override
    public Type deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JacksonException {
        //解析Json
        ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
        JsonNode jsonNode = mapper.readTree(jsonParser);
        //String tmp = mapper.writeValueAsString(jsonNode);
        final String type = jsonNode.get("@type").asText();
        if (type == null || type.length() == 0){
            return null;
        }

        if (type.startsWith("LiteralTypeEnum#")) {
            String literalType = type.substring("LiteralTypeEnum#".length());
            return LiteralTypes.valueOf(literalType);
        }

        Class clazz;
        try {
            clazz = Class.forName(type);
        } catch (ClassNotFoundException e) {
            throw new JsonParseException(jsonParser, "Class Not Found: " + type);
        }

        Object instance;
        List<String> names;
        try {
            Pair<Object, String[]> pair = createInstance( mapper, jsonNode, clazz);
            instance = pair.key;
            names = Arrays.asList(pair.value);
        } catch (Exception e) {
            throw new JsonParseException(jsonParser, "Class cannot create new instance: " + clazz);
        }

        final List<Field> fields = ClassFieldsUtils.getAllFields(clazz);
        for (Field field : fields) {
            boolean isTransient = Modifier.isTransient(field.getModifiers());
            boolean isStatic = Modifier.isStatic(field.getModifiers());
            if (isTransient || isStatic){
                continue;
            }

            if (names.contains(field.getName())){
                continue;
            }

            JsonNode fieldNode = jsonNode.get(field.getName());
            try {
                field.setAccessible(true);
                field.set(instance, mapper.treeToValue(fieldNode, field.getType()));
            } catch (IllegalAccessException ignored) {
            }
        }
        return (Type) instance;
    }

    private Pair<Object, String[]> createInstance(ObjectMapper mapper, JsonNode jsonNode, Class clazz) throws Exception {
        Constructor createrConstructor = findConstructorWithCreater(clazz);
        if (createrConstructor != null){
            createrConstructor.setAccessible(true);
            Parameter[] parameters = createrConstructor.getParameters();
            Pair<String[], Object[]> pair = createParams( mapper, jsonNode, parameters);
            String[] names = pair.key;
            Object[] params = pair.value;
            return new Pair<>(createrConstructor.newInstance(params), names);
        }

        Method createrMethod = findMethodWithCreater(clazz);
        if (createrMethod != null){
            createrMethod.setAccessible(true);
            Parameter[] parameters = createrMethod.getParameters();
            Pair<String[], Object[]> pair = createParams(mapper, jsonNode, parameters);
            String[] names = pair.key;
            Object[] params = pair.value;
            return new Pair<>(createrMethod.invoke(null, params), names);
        }

        return new Pair<>(clazz.newInstance(), new String[0]);
    }

    private static Pair<String[], Object[]> createParams( ObjectMapper mapper, JsonNode jsonNode, Parameter[] parameters) throws JsonProcessingException {
        String[] names = new String[parameters.length];
        Object[] params = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            JsonProperty jsonProperty = parameter.getAnnotation(JsonProperty.class);
            String name;
            if (jsonProperty != null){
                name = jsonProperty.value();
            } else {
                name = parameter.getName();
            }


            JsonNode fieldNode = jsonNode.get(name);
            Object fieldValue = mapper.treeToValue(fieldNode, parameter.getType());
            names[i] = name;
            params[i] = fieldValue;
        }
        return new Pair<>(names, params);
    }

    private Constructor findConstructorWithCreater(Class clazz){
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
            if (constructor.isAnnotationPresent(JsonCreator.class)) {
                return constructor;
            }
        }
        return null;
    }

    private Method findMethodWithCreater(Class clazz){
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            boolean isStatic = Modifier.isStatic(method.getModifiers());
            if (isStatic && method.isAnnotationPresent(JsonCreator.class)){
                return method;
            }
        }
        return null;
    }

    private static class Pair<K,V>{
        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }


}
